import re
import config
from Bayesian_Classifier import *

#interesting regular expressions
function_call1 = re.compile("(\w+|\w+\.\w+|\w+\.\w+\.\w+)\s*\(")
function_call2 = re.compile("\[\'?\"?(\w+)\'?\"?\]\(")
var = re.compile("var\s+(\w+)\s*[=;]\s*(.*?)[,;]")
eva = re.compile("eval\(")
evan = re.compile("\)")
unes = re.compile("unescape\(")
write = re.compile("document.write\(")

feature_dict = {"__dynamic_calls":0,"__def_use":0,"__per_readable":0,"__avg_arg_len":0,"__redirect":0,"__hidden_iframe":0,"__activex":0}

class js_features(object):	
    """extracts features from script and anomalous events"""

    def __init__(self,call_from,script=""):
	"""to make things available and visible across"""
	self.call = call_from
 	if script:				#if called from script or eval or write (script != NULL) else, initalised.
	    self.__init_extract(script)
	else:
	    self.update_feature()

	self.feature_dict = feature_dict

    def update_feature(self):
	"""updates the features based on meta, hidden iframe, object instantiation"""

	if self.call == "meta":			#count the meta redirects.
	    feature_dict['__redirect'] += 1
#things going under following comparison are not very neat. But serve the purpose for the while. I will improve if I get a better idea.
	
	if self.call == "iframe2":		#count the no. of hidden iframes that point to the another domain.
	    feature_dict['__hidden_iframe'] += 1
	if self.call == "iframe":
	    feature_dict['__redirect'] += 1 	#update the no. of redirects for each iframe call

#to count the no. of ActiveX plugins instantiated, only the ones phoneyc keeps track of are covered here.
	if self.call == "ActiveX":
	    feature_dict['__activex']   += 1


    def __init_extract(self,script):
	#call_from specifies the script is coming from <script> or from eval() or write().
	#<script> script may be packed/obfuscated - static analysis
	#script from eval() and writes() would be understandable,mostly(I assume) - dynamic analysis- these should be scored high, but are not currently.

	self.script         = script
	self.function_calls = []  	#collects all calls made.
	self.var_names      = []  	#list of all(perhaps) variable names
	self.var_values     = [] 	#list of variables values
	self.eval_arg       = []	#list of eval argument
	self.unescape_arg   = []
	self.write_arg      = []
	#i think of extracting 1. dynamic execution calls 2. string def/string use 3. len(eval-arg) 4. %human readable names
	self.extract(self.call)
	self.collect_features()
	
    def collect_features(self):
	"""collect the features extracted by extract()"""
	
	feature_dict['__dynamic_calls'] += self.count_calls()
	if self.string_use():
	    feature_dict['__def_use']   += float(self.string_def())/self.string_use()
	else:
	    feature_dict['__def_use']   += 0.0
	feature_dict['__per_readable']  += self.readable()
	feature_dict['__avg_arg_len']   += self.avg_eval()+self.avg_unescape()+self.avg_write()
	feature_dict['__redirect']	+= self.redirects()

    def extract_eval(self):
	"""extract the eval argument"""
	script = self.script
	i = 0
	n = len(script)
	while i < n:
	    match = eva.search(script,i)
	    if match:
		s = match.end()
	        e = evan.search(script,s).start()
		print "eval:",script[s:e]
		self.eval_arg.append(script[s:e])
		i = e
	    else:
		i = n
		break

    def extract_unescape(self):
        """extract the unescape argument"""
        script = self.script
        i = 0 
        n = len(script)
        while i < n:
            match = unes.search(script,i)
            if match:
                s = match.end()
                e = evan.search(script,s).start()
                print "unescape:",script[s:e]
                self.unescape_arg.append(script[s:e])
                i = e 
            else:
                i = n 
                break

    def extract_write(self):
        """extract the write argument"""
        script = self.script
        i = 0 
        n = len(script)
        while i < n:
            match = write.search(script,i)
            if match:
                s = match.end()
                e = evan.search(script,s).start()
                print "write:",script[s:e]
                self.write_arg.append(script[s:e])
                i = e 
            else:
                i = n 
                break

    def extract(self,call_from):
	"""extract the js features"""
	script = self.script
	if call_from == "script":
	    self.extract_eval()
	    self.extract_unescape()
	    self.extract_write()
	f1_list = function_call1.findall(script)
	f2_list = function_call2.findall(script)
	self.function_calls = f1_list+f2_list 			#collect all function calls in one list.
  	var_list = var.findall(script)			#this is the tuple of var name and its value, if there is one
        for v in var_list:
	    self.var_names.append(v[0])
            if v[1]: 
	        self.var_values.append(v[1])


    def count_calls(self):
	"""count dynamic function calls"""
	#includes eval, document.write, document.createElement, setTimeout, setInterval, window.onLoad, arguments.callee.toString, setAttribute
	#createAttribute, addBehaviour, appendChild
	#dom functions for object and plugin instantiation are missing here!!
	f = self.function_calls
	count = 0
	count += f.count("eval")
	count += f.count("document.write")
	count += f.count("document.createElement")
	count += f.count("setTimeout")
	count += f.count("setInterval")
	count += f.count("arguments.callee.toString")
	count += f.count("unescape")
	count += f.count("document.getElementById")
	count += f.count("location.replace")
	fc = []
        for s in f:
            calls = s.split(".")
            for c in calls:
                fc.append(c)          
	count += fc.count("setAttribute")
	count += fc.count("onLoad")
	count += fc.count("onUnload")
	count += fc.count("removeChild")
	count += fc.count("addBehaviour")
	count += fc.count("appendChild")
	return count

    def string_def(self):
	"""count string definition functions"""
	#includes substring, substr, concat, replace, fromCharCode, toString, charAt, slice, String, indexOf, split
	#all the definitions "",'',+ could be included through simple regex searches? could average string length be used as a feature??
	sd = self.function_calls
	count = 0
	fc = []
	for s in sd:
	    calls = s.split(".")
	    for c in calls:
		fc.append(c)		#for calls that have s.fromCharCode, split it on . and add it to function call list-fc
        count += fc.count("substring")
        count += fc.count("substr")
        count += fc.count("concat")
        count += fc.count("replace")
        count += fc.count("fromCharCode")
        count += fc.count("toString")
        count += fc.count("charAt")
        count += fc.count("slice")
        count += fc.count("String")
        count += fc.count("indexOf")
        count += fc.count("split")
        return count

    def string_use(self):
	"""count string use functions"""
	#includes parseInt, length, write, eval, unescape
	su = self.function_calls
	count = 0
	fc = []
	for s in su:
	    calls = s.split(".")
	    for c in calls:
		fc.append(c)
        count += fc.count("parseInt")
        count += fc.count("write")
        count += fc.count("length")
        count += fc.count("eval")
        count += fc.count("unescape")
	return count


    def readable(self):
	"""calculates the percentage of readable words"""
	num_unreadable,percent = 0,0
        list = self.var_names+self.function_calls
        for words in list:
	    word = words.split(".")
	    for w in word: 
                human_unread = self.unreadable_word(w)
                if human_unread:
                    num_unreadable += 1		#counts the unreadable words in a JS program
      	if list:
	    percent = float(num_unreadable)/len(list)*100
	return percent


    def unreadable_word(self,word):
	"""find the readability of the word"""
	#a word is readable if its less 15 characters, more than 70% alphabets, 16% < %vowels < 60%, one or two repetitions of an alphabet
	len_word = len(word)
        if len_word > 15: 
            return 1
        else:
            len_alpha = 0
            len_vowels = 0
            alpha = re.findall("[a-zA-Z]+",word)
            for a in alpha:
                len_alpha += len(a)
            per_alpha = float(len_alpha)/len_word*100
            if per_alpha < 70:
                return 1
            else:
                vowels = re.findall(r"[aeiou]+",word,re.I)
                for v in vowels:
                    len_vowels += len(v)
                per_vowels = float(len_vowels)/len_word*100
                if per_vowels > 60 or per_vowels < 16:
                    return 1
                else:
                    for i in range(len_word-2): 
                        if word[i].lower() == word[i+1].lower():
                            if word[i+1].lower() == word[i+2].lower():
                                return 1
                            else:
                                i = i+1
                    return 0

    def avg_eval(self):
	"""find average length of the eval arguments"""
	len_arg = 0
	eval_list = self.eval_arg
	for arg in eval_list:
	    len_arg += len(arg)
	if eval_list:
	    return float(len_arg)/len(eval_list)
	else:
	    return 0.0

    def avg_unescape(self):
	len_arg = 0
	unescape_list = self.unescape_arg
	for arg in unescape_list:
 	    len_arg += len(arg)
	if unescape_list:
	    return float(len_arg)/len(unescape_list)
	else:
	    return 0.0

    def avg_write(self):
	len_arg = 0
	write_list = self.write_arg
	for arg in write_list:
	    len_arg += len(arg)
	if write_list:
	    return float(len_arg)/len(write_list)
	else:
	    return 0.0	

    def redirects(self):
	"""count the redirections"""
	#present method counts the redirects occuring through 1. document.location, 2. location.href 3. location.replace 4. iframe
	#to imprrove for HTTP status 3XX redirections
	redirect = 0
 	f = self.function_calls
	redirect += f.count("location.replace")
	redirect += f.count("location.href")
	redirect += f.count("document.location")	
	redirect += f.count("window.location")
	return redirect	

#    def count_object(self):
#	"""counts the no. of plugins or objects instantiated"""
#	count = feature_dict["__activex"]
#	return count	
